/***
* Feel free to use this code however you wish.
* Created by Alie Tan (www(dot)alietan(dot)com)
*/

#include <ma.h>
#include <mastdlib.h>
#include <conprint.h>
#include <MAUtil/Moblet.h>

#include <MoBox2D/Box2D.h>

#include "PolyTool.h"
#include "Sprite.h"

#define TIMER_PERIOD 1
#define RGBA(r, g, b, a) ((r)<<16)|((g)<<8)|((b))|((a)<<24)

#define MAX_VERTICES	255
#define MAX_BODY		10

using namespace MAUtil;


class MyMoblet : public Moblet, TimerListener
{
public:
	MyMoblet()
	{
		// Get screen dimensions.
		MAExtent size = maGetScrSize();
		// Extract the screen width
		screenWidth = EXTENT_X(size);
		// Extract the screen height
		screenHeight = EXTENT_Y(size);

		// Create the image in which the cube will be drawn, to draw multiple versions of it.
		maCreateDrawableImage(1, screenWidth, screenHeight);

		initWorld();
		addTimer(this, TIMER_PERIOD, 0);
	}

	~MyMoblet()
	{
	    delete world;
	}

public:
	void initWorld()
	{
		// Create a world
		b2Vec2 gravity = b2Vec2(0.0f, 60.0f);
		bool doSleep = true;
		world = new b2World(gravity, doSleep);

		// Create edges around the entire screen
		b2BodyDef groundBodyDef;
		groundBody = world->CreateBody(&groundBodyDef);
		b2PolygonShape groundBox;
		b2FixtureDef boxShapeDef;
		boxShapeDef.shape = &groundBox;
		groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(screenWidth, 0));
		groundBody->CreateFixture(&boxShapeDef);
		groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(0, screenHeight));
		groundBody->CreateFixture(&boxShapeDef);
		groundBox.SetAsEdge(b2Vec2(0, screenHeight), b2Vec2(screenWidth, screenHeight));
		groundBody->CreateFixture(&boxShapeDef);
		groundBox.SetAsEdge(b2Vec2(screenWidth, screenHeight), b2Vec2(screenWidth, 0));
		groundBody->CreateFixture(&boxShapeDef);
	}

	double degreeToRadian(double degree)
	{
		double radian = 0;
		radian = degree * (M_PI/180);
		return radian;
	}

	void runTimerEvent()
	{
	    // Now sim code
		timeStep = 1.0f / 60.0f;

		// Set the following draw calls to draw to the drawable image created (draw target 1).
    	maSetDrawTarget(1);

    	maSetColor(0x0088ff);
		maFillRect(0, 0, screenWidth, screenHeight);

		if(polyLines.size() >= 2 && drawLines)
		{
			MAPoint2d points[polyLines.size()];
			for(int i = 1; i < polyLines.size(); i++)
			{
				maSetColor(0xff0000);
				maLine((int)polyLines[i-1].x, (int)polyLines[i-1].y, (int)polyLines[i].x, (int)polyLines[i].y);
			}
		}

		world->Step(timeStep, 1, 1);

		for(b2Body *b = world->GetBodyList(); b; b=b->GetNext())
		{
			for(b2Fixture *f = b->GetFixtureList(); f; f=f->GetNext())
			{
				b2Shape *s = f->GetShape();
				if(s->GetType() == b2Shape::e_polygon)
				{
					b2PolygonShape *p = (b2PolygonShape*)s;
					int vertexCount = p->GetVertexCount();
					if(vertexCount > 2)
					{
						CSprite *sprite = (CSprite *)b->GetUserData();
						MAPoint2d points[vertexCount];
						for(int i = 0; i < vertexCount; i++)
						{
							b2Vec2 vec = p->GetVertex(i);
							b2Vec2 world = b->GetWorldPoint( vec );
							int x = (int)(world.x);
							int y = (int)(world.y);
							points[i].x = x;
							points[i].y = y;
						}
						maSetColor(sprite->getColor());
						maFillTriangleFan(points, vertexCount);
					}
				}
			}
		}

		// Set the following draw calls to draw to the screen (draw target 0).
		maSetDrawTarget(0);

		MARect srcRect = {0, 0, screenWidth, screenHeight};
		MAPoint2d dstPoint = {0, 0};
		maDrawImageRegion(1, &srcRect, &dstPoint, TRANS_NONE);

		/// Updates the screen
		maUpdateScreen();

		/// Keep the backlight alive.
		maResetBacklight();
	}

	b2Body *getBodyAtMouse(b2Vec2 p)
	{
		b2Body *body = NULL;
		bool finish = false;
		for(b2Body *b = world->GetBodyList(); b&&!finish; b=b->GetNext())
		{
			for (b2Fixture* f = b->GetFixtureList(); f; f = f->GetNext())
			{
				if(f->TestPoint(p))
				{
					body = b;
					finish = true;
					break;
				}
			}
		}
		return body;
	}

	void pointerPressEvent(MAPoint2d p)
	{
		b2Body *body = getBodyAtMouse(b2Vec2(p.x, p.y));
	    if (body)
	    {
			b2MouseJointDef mouseJointDef;
			mouseJointDef.bodyA = groundBody;
			mouseJointDef.bodyB = body;
			mouseJointDef.target.Set(p.x, p.y);
			mouseJointDef.maxForce = 1000.0f * body->GetMass();
			mouseJointDef.dampingRatio = 0.0f;
			mouseJointDef.frequencyHz = 60.0f;
			mouseJoint = (b2MouseJoint *)world->CreateJoint(&mouseJointDef);
			body->SetAwake(true);
	    }
	    else
	    {
			drawLines = true;
			polyLines.clear();

			b2Vec2 point;
			point.x = p.x;
			point.y = p.y;
			polyLines.add(point);
	    }
	}

	void pointerMoveEvent(MAPoint2d p)
	{
		b2Vec2 pointerPos = b2Vec2(p.x, p.y);
		if(mouseJoint)
		{
			polyLines.clear();
			mouseJoint->SetTarget(pointerPos);
			return;
		}

		b2Vec2 startPoint(polyLines[polyLines.size()-1]);
		b2Vec2 endPoint;
		endPoint.x = p.x;
		endPoint.y = p.y;

		if(PolyTool::lengthOfLine(startPoint, endPoint) > 15.0f)
		{
			polyLines.add(endPoint);
		}
	}

	void makeSimpleBody(b2Body *body, b2Vec2 *vertices, int vertexCount)
	{
		b2PolygonShape polyShape;
		polyShape.Set(vertices, vertexCount);

		b2FixtureDef boxShapeDef;
		boxShapeDef.shape = &polyShape;
		boxShapeDef.density = 100.0f;
		boxShapeDef.friction = 0.0f;
		boxShapeDef.restitution = 0.0f;
		body->CreateFixture(&boxShapeDef);
	}

	void makeComplexBody(b2Body *body, b2Vec2 *vertices, int vertexCount)
	{
		b2Vec2 processedVerts[MAX_VERTICES];
		int processedVertexCount = PolyTool::getTriangulatedPoly(vertices, processedVerts, vertexCount);
		if(processedVerts != NULL)
		{
			for(int i = 0; i < processedVertexCount/3; i++)
			{
				b2PolygonShape polyShape;
				b2Vec2 triangleVertices[3];
				int index = i*3;
				triangleVertices[0].Set(processedVerts[index].x, processedVerts[index].y);
				triangleVertices[1].Set(processedVerts[index+1].x, processedVerts[index+1].y);
				triangleVertices[2].Set(processedVerts[index+2].x, processedVerts[index+2].y);
				makeSimpleBody(body, triangleVertices, 3);
			}
		}
		else
		{
			// The polygon is bad somehow. Probably overlapping segments.
			// So let's recurse with the convex hull of the bad poly.
			processedVertexCount = PolyTool::getConvexPoly(vertices, processedVerts, vertexCount);
			makeComplexBody(body, processedVerts, processedVertexCount);
		}
	}

	void pointerReleaseEvent(MAPoint2d p)
	{
		if (mouseJoint)
		{
			world->DestroyJoint(mouseJoint);
			mouseJoint = NULL;
		}

		drawLines = false;

		int vertexCount = polyLines.size();
		if(vertexCount > MAX_VERTICES)
		{
			// Do nothing
			return;
		}

		srand(maLocalTime());
		CSprite *sprite = new CSprite(RGBA(255&rand(), 255&rand(), 255&rand(), 0));

		b2BodyDef bodyDef;
		bodyDef.type = b2_dynamicBody;
		bodyDef.userData = sprite;
		b2Body *body = world->CreateBody(&bodyDef);

		b2Vec2 vertices[MAX_VERTICES];
		for(int i = 0; i < vertexCount; i++)
		{
			vertices[i].Set(polyLines[i].x, polyLines[i].y);
		}

		// If there are more than 8 vertices, its a complex body
		if(vertexCount > 8)
		{
			makeComplexBody(body, vertices, vertexCount);
		}
		else
		{
			if(PolyTool::isPolyConvex(vertices, vertexCount) && !PolyTool::isPolyClockwise(vertices, vertexCount))
			{
				makeSimpleBody(body, vertices, vertexCount);
			}
			else
			{
				makeComplexBody(body, vertices, vertexCount);
			}
		}
	}

private:
	b2World *world;
	b2Body *groundBody;
	b2MouseJoint *mouseJoint;

	Vector<b2Vec2> polyLines;

	float32 timeStep;

	int screenWidth;
	int screenHeight;

	bool drawLines;
};

extern "C" int MAMain()
{
	Moblet::run(new MyMoblet());
}
